//-------------------------------------------------------------------
//	Copyright (c) 2013  Zhirnov Andrey
//	This file is part of the "Noise Library" project.
//	See copyright notice in "linNoiseCL.h".
//-------------------------------------------------------------------

#pragma once

#include "../CSource.h"

namespace Noise
{
	
    const real DEFAULT_VORONOI_DISPLACEMENT = 1.0;
    const real DEFAULT_VORONOI_FREQUENCY = 1.0;
    const int DEFAULT_VORONOI_SEED = 0;

	//
	// Function Source for Voronoi cells.
	//

	class CSourceVoronoi : public CSource
	{
	// types
	protected:
		typedef CSourceVoronoi	Self;


	// variables
	protected:
		real		_displacement;		// Scale of the random displacement to apply to each Voronoi cell.
		real		_frequency;			// Frequency of the seed points.
		int			_seed;				// Seed value used by the coherent-noise function to determine the positions of the seed points.
		bool		_enableDistance;	// Determines if the distance from the nearest seed point is applied to the output value.
		
		static const
			ESource	_sourceType = VORONOI;

	// methods
	public:
		CSourceVoronoi () :
			_displacement(DEFAULT_VORONOI_DISPLACEMENT),
			_frequency(DEFAULT_VORONOI_FREQUENCY),
			_seed(DEFAULT_VORONOI_SEED),
			_enableDistance(false)
		{}

		~CSourceVoronoi ()
		{}

		ESourceState GetState () const
		{
			return SOURCE_COMPLETE;
		}
		
		virtual void Delete ()
		{
		}

		virtual EResult GetInteger (ESourceQuery type, int *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case SOURCE_STATE :
					v[0] = GetState();
					return OK;

				case SOURCE_TYPE :
					v[0] = _sourceType;
					return OK;

				case SOURCES_COUNT :
					v[0] = 1;
					return OK;

				case INPUT_SOURCES :
					v[0] = 0;
					return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual EResult SetArg (EArgument type, const real *v, int count)
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case ARG_DISPLACEMENT :	_displacement = v[0];	return OK;
				case ARG_FREQUENCY :	_frequency = v[0];		return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual EResult SetArg (EArgument type, const int *v, int count)
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case ARG_SEED :				_seed = v[0];				return OK;
				case ARG_ENABLE_DISTANCE :	_enableDistance = !!v[0];	return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual EResult GetArg (EArgument type, real *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case ARG_DISPLACEMENT :	v[0] = _displacement;	return OK;
				case ARG_FREQUENCY :	v[0] = _frequency;		return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual EResult GetArg (EArgument type, int *v, int count) const
		{
			CHECK_ARGUMENT_R( count >= 1, INVALID_VALUE );

			switch ( type )
			{
				case ARG_SEED :				v[0] = _seed;			return OK;
				case ARG_ENABLE_DISTANCE :	v[0] = _enableDistance;	return OK;
			}
			return INVALID_ENUM;
		}
		
		virtual CSourcePtr Clone () const override
		{
			Self *	src = new Self();

			src->_displacement	 = _displacement;	
			src->_frequency		 = _frequency;	
			src->_seed			 = _seed;
			src->_enableDistance = _enableDistance;

			return src;
		}
		
		virtual void PreBuild (const TPreBuildData &data) override
		{
		}

		virtual bool BuildFunction (TFunctionData &data) override
		{
			Utils::NoiseGenFunctions( data );

			if ( data.AddSourceType( _sourceType ) )
			{
				data.source << TOSTRING(
					real Voronoi (real3 vPoint, real _displacement, real _frequency, int _seed, bool _enableDistance)\n
					{\n
						real3 p		= vPoint * _frequency;\n
						int3  init	= (int3)( vPoint.x > 0.0 ? (int)vPoint.x : (int)vPoint.x - 1,\n
											  vPoint.y > 0.0 ? (int)vPoint.y : (int)vPoint.y - 1,\n
											  vPoint.z > 0.0 ? (int)vPoint.z : (int)vPoint.z - 1 );\n
						int3  cur	= (int3)(0);\n
						\n
						real minDist = (real)(2147483647.0);\n
						real3 candidate = (real3)(0);\n
						\n
						for (cur.z = init.z - 2; cur.z <= init.z + 2; ++cur.z)\n
						{\n
							for (cur.y = init.y - 2; cur.y <= init.y + 2; ++cur.y)\n
							{\n
								for (cur.x = init.x - 2; cur.x <= init.x + 2; ++cur.x)\n
								{\n
									real3 pos		= (real3)( ValueNoise3DV3( cur, _seed ),
															   ValueNoise3DV3( cur, _seed + 1 ),
															   ValueNoise3DV3( cur, _seed + 2 ) )
														+ convert_real3(cur);\n
									\n
									real3 distVec	= pos - vPoint;\n
									real  dist		= dot( distVec, distVec );\n
									\n
									if ( dist < minDist )\n
									{\n
										minDist = dist;\n
										candidate = pos;\n
									}\n
								}\n
							}\n
						}\n
						\n
						const real SQRT_3 = (real)(1.73205080756887729352);\n
						\n
						real value;\n
						if ( _enableDistance )\n
						{\n
							real3 distVec = candidate - vPoint;\n
							value = length(distVec) * SQRT_3 - (real)(1.0);\n
						}\n
						else\n
							value = 0.0;\n
						\n
						return value + (_displacement * ValueNoise3DV3( convert_int3(floor(candidate)), 0 ) );\n
					}\n\n
				);
			}

			CSource::BuildFunctionHeader( data.source, _id )
				<< " ( Voronoi( vPoint, "
				<< _displacement << ", " << _frequency << ", "
				<< _seed << ", " << _enableDistance << " ) )\n";

			return true;
		}
		
		NATIVE(
		template < typename Real, Real (CSource::*NativeFunction)(const TVec<Real,3> &) >
		Real NativeFunction (const TVec<Real,3> &vPoint)
		{
			using namespace NativeUtils;

			typedef TVec<Real,3>	Real3;

			Real3 p		= vPoint * _frequency;
			int3  init	= int3(	vPoint.x > 0.0 ? (int)vPoint.x : (int)vPoint.x - 1,
								vPoint.y > 0.0 ? (int)vPoint.y : (int)vPoint.y - 1,
								vPoint.z > 0.0 ? (int)vPoint.z : (int)vPoint.z - 1 );
			int3  cur	= int3(0);

			Real minDist = Real(2147483647.0);
			Real3 candidate = Real3(0);
			
			for (cur.z = init.z - 2; cur.z <= init.z + 2; ++cur.z)
			{
				for (cur.y = init.y - 2; cur.y <= init.y + 2; ++cur.y)
				{
					for (cur.x = init.x - 2; cur.x <= init.x + 2; ++cur.x)
					{
						Real3 pos		= Real3( ValueNoise3D<Real>( cur, _seed ),
												 ValueNoise3D<Real>( cur, _seed + 1 ),
												 ValueNoise3D<Real>( cur, _seed + 2 ) )
											+ cur.To<Real3>();

						Real3 distVec	= pos - vPoint;
						Real  dist		= Dot( distVec, distVec );

						if ( dist < minDist )
						{
							minDist = dist;
							candidate = pos;
						}
					}
				}
			}

			Real value;
			if ( _enableDistance )
			{
				Real3 distVec = candidate - vPoint;
				value = distVec.Length() * Sqrt_3<Real>() - Real(1.0);
			}
			else
				value = 0.0;

			return value + (Real(_displacement) * ValueNoise3D<Real>( Floor(candidate).To<int3>(), 0 ) );
		}

		virtual float32 NativeFunction32 (const fvec3 &vPoint) override
		{
			return NativeFunction< float32, &CSource::NativeFunction32 >( vPoint );
		}

		virtual float64 NativeFunction64 (const dvec3 &vPoint) override
		{
			return NativeFunction< float64, &CSource::NativeFunction64 >( vPoint );
		})

		static bool CreateSource (CSourcePtr &src, CContext *context, ESource type)
		{
			ASSERT( type == _sourceType );

			src = new Self();
			return true;
		}
	};

}	// Noise